Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADR-16] Refactor Folders #3732

Closed
wants to merge 37 commits into from
Closed

[ADR-16] Refactor Folders #3732

wants to merge 37 commits into from

Conversation

Hinton
Copy link
Member

@Hinton Hinton commented Oct 10, 2022

Type of change

- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other

Objective

Refactors the Folder domain to follow the new method described in ADR-16. Please read the ADR for some background and the motivation for this move.

This PR focuses on introducing the core services needed for the new data model, and migrates the Folder domain.

The way this is implemented is by adding a generic method decryptDomain to CryptoService. Which currently fetches the relevant encryption key and calls decryptDomain on EncryptionService. Which in turns calls the static method FolderView.decrypt(encryptionService, key, domain). Which performs the actual mapping from domain to view.

Fetching of the encryption key is implemented for organizations and personal owned items. Personal items gets a null value assigned while organization items provides the organization ID as the identifier. In the future we can support more keys by adding more lookup locations.

Encryption works in a similar fashion using the encryptView which fetches the key and proxies the request to EncryptionService which calls decrypt on the view.

Why not just put the decrypt on FolderService? The FolderService already has a responsibility, and that is to maintain the state of Folders. We shouldn't give it more responsibilities, particularly not when the EncryptService is already responsible for encrypting and decrypting things.

Before you submit

  • Please add unit tests where it makes sense to do so (encouraged but not required)
  • If this change requires a documentation update - notify the documentation team
  • If this change has particular deployment requirements - notify the DevOps team

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also been noodling away on this, please review #3738 as an alternative proposal and let me know what you think. We can line up a call to discuss in more detail if it's useful.

The biggest difference is that I don't want to have this pattern where we:

  • pass an object to a decrypt method
  • that method passes a service into a method on the object
  • there are several levels of nested decrypt calls, passing down the same parameters each time, which you have to trace to find the actual decryption call still nested in EncString.

To me that is the real source of the tech debt here, it feels like decryption happens from the inside out, it's difficult to understand and extend. ContainerService is just a symptom of that. I'd like to flatten it out to a method which acts on an object.

libs/common/src/models/view/folderView.ts Outdated Show resolved Hide resolved
libs/common/src/services/crypto.service.ts Outdated Show resolved Hide resolved
@Hinton
Copy link
Member Author

Hinton commented Oct 11, 2022

One goal that hasn't been noted in this PR, is the potential use case for a domain model to have multiple views with different amount of data.

I kept the decrypt method for a few reasons,

  1. to limit the scope of the refactor.
  2. Support objects that decrypt differently attachments seems to have some custom logic, and there has been some discussion around potentially having an EncObject which contains a json blob instead of individual fields.

@eliykat
Copy link
Member

eliykat commented Oct 12, 2022

Support objects that decrypt differently attachments seems to have some custom logic, and there has been some discussion around potentially having an EncObject which contains a json blob instead of individual fields.

Just a note on this topic, I'd like to avoid having lots of custom logic here or different ways of encrypting objects. I'd rather define a few ways of encrypting objects (e.g. individual EncStrings or an encrypted blob) and make people implement those, rather than give every object the ability to do something different.

I probably have to experiment with more complex objects though to make sure that we can impose this kind of uniformity across our domain objects.

One goal that hasn't been noted in this PR, is the potential use case for a domain model to have multiple views with different amount of data.

Nice idea!

@MGibson1
Copy link
Member

I don't particularly like that it's the View's responsibility to determine what data on the Domain is encrypted and what is not. To me, #3738 and this are complimentary -- #3732 describes the data flow and #3738 is a better interface for defining encrypted data/decryption. @eliykat, the big change that would be necessary in your PR is updating the decryptable interface to belong on the View instead of the Domain. This would enable @Hinton's idea of multiple views for a given domain. The issue is how do we expose to the encryptservice which fields to decrypt and which are not needed? Not sure on that one, yet...

The other issue I see here is this doesn't solve the central issue of how to grab the key to my liking. I think I'd like to see an interface on Domain that determines the appropriate key, given a 2d array of [Id, Key][].

@Hinton
Copy link
Member Author

Hinton commented Oct 12, 2022

Having an interface on Domain to determine which key is used to encrypt things seems reasonable, it could be argued it's part of the domain model.

Something that came to mind are Attachments, Name is an EncString, but the key has custom logic, and the data is encrypted using the key. Not sure if we have more edge cases for how things are encrypted/decrypted.

…crypt-test

# Conflicts:
#	apps/cli/src/commands/create.command.ts
#	apps/cli/src/commands/edit.command.ts
#	libs/common/src/abstractions/crypto.service.ts
#	libs/common/src/abstractions/folder/folder.service.abstraction.ts
#	libs/common/src/importers/bitwardenJsonImporter.ts
#	libs/common/src/models/domain/folder.ts
#	libs/common/src/models/view/folder.view.ts
#	libs/common/src/services/crypto.service.ts
#	libs/common/src/services/folder/folder.service.ts
@Hinton Hinton requested a review from eliykat October 31, 2022 10:06
Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one minor comment so far, unfortunately I didn't get to spend a lot of time on this today. Although no surprises immediately jump out at me, which is good. I'll continue Monday.

libs/common/src/models/view/folder.view.ts Outdated Show resolved Hide resolved
libs/common/src/models/domain/enc-string.ts Outdated Show resolved Hide resolved
libs/common/src/abstractions/crypto.service.ts Outdated Show resolved Hide resolved
libs/common/src/models/domain/folder.ts Show resolved Hide resolved
libs/common/src/models/domain/folder.ts Outdated Show resolved Hide resolved
libs/common/src/models/view/encryptable.ts Outdated Show resolved Hide resolved
libs/common/src/models/view/folder.view.ts Outdated Show resolved Hide resolved
libs/common/src/models/view/folder.view.ts Outdated Show resolved Hide resolved
@eliykat
Copy link
Member

eliykat commented Nov 10, 2022

I'm still a little unsure what the end state of CryptoService and EncryptService is. The duplicated interfaces between them are starting to get messy.

iirc, the last we discussed was potentially changing CryptoService to be KeyService, then have EncryptService depend on KeyService so that it can fetch its own keys (with a static key store for web workers). That brought up circular dependency issues that we did not fully resolve. (see my notes). While we don't need to do all of this in 1 PR, I'd like to know where we're going with it.

@Hinton
Copy link
Member Author

Hinton commented Nov 10, 2022

@eliykat I understand and I didn't want to muddle this PR with any refactoring of CryptoService.

I agree with the move to KeyService and have EncryptService depend on KeyService. But I view that as something we'll get around to at a later stage. I would like to refactor CryptoService to observables and do this refactor prior which should make that change fairly simple.

patch.diff Outdated Show resolved Hide resolved
@Hinton Hinton changed the base branch from master to feature/decrypt-refactor December 2, 2022 15:53
@Hinton
Copy link
Member Author

Hinton commented Dec 2, 2022

I've removed the Cipher changes from this again, and changed the target to a new feature branch. My thinking is that we'll review this to ratify the general patterns, get QA to begin testing folders while I move the cipher portions to a new PR and we can review it prior to merging the feature branch.

This lets QA test both potions in isolation and we don't introduce the change until we've migrated Ciphers which prevents us from having 3 ways of doing encryption/decryption of domain models.

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly minor comments given that the cipher changes have been taken out and I think we've discussed the folder stuff quite a bit already.

To be honest I'm struggling with the various type/interface names (Decryptable/Encryptable) etc, but I don't have any better suggestions for them. I'll chew on it though.

libs/common/src/services/crypto.service.ts Outdated Show resolved Hide resolved
libs/common/src/services/crypto.service.ts Outdated Show resolved Hide resolved
folder.id = this.id;
folder.revisionDate = this.revisionDate;

folder.name = this.name != null ? await encryptService.encrypt(this.name, key) : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can also use NullableFactory here as well, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it's not a constructor. We could make a nullable factory that accepts an argument with a factory in it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. It's a similar pattern though (call a method if value isn't null), and this ternary condition will be repeated 100 times as we encrypt properties on objects under this new pattern.

libs/common/src/interfaces/crypto.interface.ts Outdated Show resolved Hide resolved
import { EncryptService } from "../abstractions/encrypt.service";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";

export function nullableFactory<T extends new (...args: any) => any>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullableFactory seems broadly useful to me, I think it should stand outside of the crypto-specific code.

@Hinton
Copy link
Member Author

Hinton commented Dec 5, 2022

We can discuss it in the future PR, but I don't want to mix in any knowledge of views in the domain. Since domain is in the center and the ambition to support multiple views per domain.

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, but I'd like a final review from @MGibson1.

libs/common/src/models/domain/folder.ts Show resolved Hide resolved
libs/common/src/models/view/folder.view.ts Show resolved Hide resolved
apps/cli/src/commands/edit.command.ts Outdated Show resolved Hide resolved
…crypt-test

# Conflicts:
#	apps/web/src/app/settings/change-password.component.ts
@Hinton Hinton force-pushed the feature/decrypt-test branch from af7c425 to 43850ad Compare January 12, 2023 12:53
@Hinton Hinton requested review from eliykat and MGibson1 January 12, 2023 16:13
@Hinton Hinton mentioned this pull request Jan 14, 2023
@Hinton Hinton changed the base branch from feature/decrypt-refactor to master January 27, 2023 12:30
@Hinton Hinton changed the title Proposal - Decrypt refactor [ADR-16] Refactor Folders Jan 27, 2023
@eliykat
Copy link
Member

eliykat commented Jun 13, 2023

Removing myself from reviewers on the understanding this will be done in the SDK instead.

@eliykat eliykat removed their request for review June 13, 2023 23:27
@Hinton Hinton closed this Sep 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants